#include <string.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
#include <error.cpp>
#include <utils.cpp>

static void cb_digest_names_len(const EVP_MD *md, const char *from,
	const char *to,
	void *x) {
	if (md) {
		*((int *)x) += strlen(EVP_MD_name(md)) + 1;
	}
}

static void cb_digest_names(const EVP_MD *md, const char *from,
	const char *to, void *x) {
	if (md) {
		if (strlen((char *)x) > 0) {
			strcat((char *)x, ":");
		}
		strcat((char *)x, EVP_MD_name(md));
	}
}

static char *get_digest_names() {
	char *ret = NULL;
	int len = 0;
	EVP_MD_do_all_sorted(cb_digest_names_len, &len);
	if (!(ret = (char*)OPENSSL_zalloc(len))) {
		return NULL;
	}
	EVP_MD_do_all_sorted(cb_digest_names, ret);
	return ret;
}

extern void _OPENSSL_free(void *addr);

void get_digest_names(vector<string>& digests)
{
    auto names = get_digest_names();
    const char* s = ":";
    split_vector(digests,names, s);
    _OPENSSL_free(names);
}
int get_digest_length(const char* name)
{
    auto md = EVP_get_digestbyname(name);
    if ( md == nullptr)
    {
        throw get_errors();
    }

    return int(EVP_MD_size(md));
}
int get_digest_block_size(const char* name)
{
    auto md = EVP_get_digestbyname(name);
    if ( md == nullptr)
    {
        throw get_errors();
    }

    return int(EVP_MD_block_size(md));
}


EVP_MD_CTX* new_digest_context(const char* name)
{
    auto md = EVP_get_digestbyname(name);

    if ( md == nullptr)
    {
        throw get_errors();
    }

    auto ctx = EVP_MD_CTX_new();
    if ( ctx == nullptr )
    {
        throw get_errors();
    }
    if ( 1 != EVP_DigestInit_ex(ctx, md, nullptr))
    {
        throw get_errors();
    }

    return ctx;
}

void free_md_ctx(EVP_MD_CTX* ctx)
{
    EVP_MD_CTX_free(ctx);
}

void update_md_ctx(EVP_MD_CTX* ctx,const unsigned char* data)
{
    if (sizeof(data) == 0)
    {
        return;
    }

    if( 1!= EVP_DigestUpdate(ctx, data, strlen((const char*)data)) )
    {
        throw get_errors();
    }
}

void final_md_ctx(EVP_MD_CTX* ctx, unsigned char* outbuf, unsigned int& outlen)
{
    unsigned char tmp[2048];
    if ( 1 != EVP_DigestFinal_ex(ctx, tmp, &outlen) )
    {
        throw get_errors();
    }
    memcpy(outbuf, tmp, outlen);
}


void reset_md_ctx(EVP_MD_CTX* ctx)
{
    auto md = EVP_MD_CTX_md(ctx);
    
    if(md == nullptr)
    {
        throw get_errors();
    }

    if ( 1 != EVP_DigestInit_ex(ctx, md, nullptr) )
    {
        throw get_errors();
    }
    
}




